/*
 * store_file.c
 *
 *  Created on: Feb 16, 2013
 *      Author: redchen
 */

#include <lfs.h>
#include <list.h>
#include <iterator_utils.h>
#include <emalloc.h>
#include <string.h>
#include <stddefine.h>
#include <stdio.h>
#include <io.h>
#include <timer.h>
#include <pthread.h>
#include "store_file.h"

/**
 * Describe the whole file of information
 * Size : 300 B
 */
typedef struct
{
    char namespace[FILENAME_LEN];
    StoreFileType type;
    int32_t status;
    int32_t allocated_size;
    int32_t used_size;
    int32_t end_block_offset;
    int32_t indexer_offset;
    int32_t trailer_offset; //need to stored to end of the file
    int64_t start_timestamp;
    int64_t end_timestamp;
} Trailer;

/*
 * Size: 16 B
 */
typedef struct
{
    int32_t size;
    int32_t offset; //beginning of the block in file
    int64_t timestamp;
} BlockIndex;

typedef struct
{
    int32_t num;
    List* indexs; //List<BlockIndex>
} Indexer;

struct _StoreFile_
{
    char path[FILENAME_LEN];
    Indexer* indexer;
    Trailer* trailer;
    pthread_rwlock_t rw_lock;
} ;

static void fp_flush(FILE* fp, int32_t offset, int32_t size, void* data)
{
    fseek(fp, offset, SEEK_SET);
    fwrite(data, size, 1, fp);
}

static void fp_load(FILE* fp, int32_t offset, int32_t size, void** data)
{
    fseek(fp, offset, SEEK_SET);
    fread(data, size, 1, fp);
}

static void block_index_flush(FILE* fp, BlockIndex* index)
{
    fwrite(index, sizeof(BlockIndex), 1, fp);
}

static BlockIndex* block_index_load(FILE* fp)
{
    BlockIndex* index = emalloc(sizeof(BlockIndex));
    fread(index, sizeof(BlockIndex), 1, fp);
    return index;
}

static Indexer* indexer_create()
{
    Indexer* indexer = emalloc(sizeof(Indexer));
    indexer->num = 0;
    indexer->indexs = list_new();
    return indexer;
}

static void indexer_flush(FILE* fp, Indexer* indexer)
{
    fwrite(&indexer->num, sizeof(indexer->num), 1, fp);
    iterator_list(indexer->indexs)
    {
        BlockIndex* index = list_next(indexer->indexs);
        block_index_flush(fp, index);
    }
}

static Indexer* indexer_load(FILE* fp)
{
    Indexer* indexer = emalloc(sizeof(Indexer));
    fread(&indexer->num, sizeof(indexer->num), 1, fp);
    indexer->indexs = list_new();
    for (int32_t i = 0; i < indexer->num; i++)
    {
        list_push_back(indexer->indexs, block_index_load(fp));
    }
    return indexer;
}

//trailer

static Trailer* trailer_create(const char* namespace, StoreFileType type, int32_t status, int32_t end_block_offset, int32_t indexer_begin_offset, int32_t used_size, int32_t allocated_size, int32_t trailer_offset, int64_t start_timestamp, int64_t end_timestamp)
{
    Trailer* trailer = emalloc(sizeof(Trailer));
    memcpy(trailer->namespace, namespace, sizeof(trailer->namespace));
    trailer->type = type;
    trailer->used_size = used_size;
    if (type == STORE_FILE_NORMAL)
    {
        allocated_size = used_size;
    }
    trailer->status = status;
    trailer->allocated_size = allocated_size;
    trailer->end_block_offset = end_block_offset;
    trailer->indexer_offset = indexer_begin_offset;
    trailer->trailer_offset = trailer_offset;
    trailer->start_timestamp = start_timestamp;
    trailer->end_timestamp = end_timestamp;
    return trailer;
}

static Trailer* trailer_load(FILE* fp)
{
    Trailer* trailer = emalloc(sizeof(Trailer));
    fread(trailer, sizeof(Trailer), 1, fp);
    return trailer;
}

static void trailer_flush(FILE* fp, Trailer* trailer)
{
    fwrite(trailer, sizeof(Trailer), 1, fp);
}

static int32_t indexer_get_size(Indexer* indexer)
{
    int32_t size = 0, i = 0;
    size += sizeof(indexer->num);
    for (; i < list_size(indexer->indexs); i++)
    {
        size += sizeof(BlockIndex);
    }
    return size;
}

int32_t store_file_get_free_size(StoreFile* file)
{
    int32_t free_size = file->trailer->allocated_size - file->trailer->used_size;
    return free_size;
}

StoreFile* store_file_create(const char* path, const char* namespace, StoreFileType type, int32_t size)
{
    /*
    if (io_check_file_exist(path))
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "'%s' is exist, can not create file.", path);
        CheckParam(false, buf);
        return NULL;
    }
    */
    FILE *fp = NULL;
    if ((fp = fopen(path, "wb")) == NULL)
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "Can not create '%s'.", path);
        CheckParam(false, buf);
        return NULL;
    }
    StoreFile* file = emalloc(sizeof(StoreFile));
    memcpy(file->path, path, sizeof(file->path));

    file->indexer = indexer_create();

    int32_t indexer_size = indexer_get_size(file->indexer);
    int32_t trailer_size = sizeof(Trailer);

    if (type == STORE_FILE_NORMAL)
    {
        size = trailer_size + indexer_size;
    }
    else
    {
        size = (size >= (trailer_size + indexer_size)) ? size : (trailer_size + indexer_size);
    }

    int32_t indexer_start_offset = size - trailer_size - indexer_size;
    int32_t used_size = trailer_size + indexer_size;
    int32_t trailer_offset = size - trailer_size;
    int64_t timestamp = timer_get_micro_second();
    Trailer* trailer = trailer_create(
            namespace,
            type,
            OP_X,
            0,
            indexer_start_offset,
            used_size,
            size,
            trailer_offset,
            timestamp,
            timestamp);
    file->trailer = trailer;

    fseek(fp, file->trailer->indexer_offset, SEEK_SET);
    indexer_flush(fp, file->indexer);

    fseek(fp, file->trailer->trailer_offset, SEEK_SET);
    trailer_flush(fp, file->trailer);
    fclose(fp);
    pthread_rwlock_init(&file->rw_lock, NULL);
    return file;
}

StoreFile* store_file_open(const char* path)
{
    StoreFile* file = emalloc(sizeof(StoreFile));
    FILE* fp = NULL;
    if ((fp = fopen(path, "rb")) == NULL)
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "Can not open the file '%s'", path);
        CheckParam(false, buf);
        return NULL;
    }
    memcpy(file->path, path, sizeof(file->path));
    int32_t trailer_size = sizeof(Trailer);
    fseek(fp, -trailer_size, SEEK_END);
    Trailer* trailer = trailer_load(fp);

    fseek(fp, trailer->indexer_offset, SEEK_SET);
    Indexer* indexer = indexer_load(fp);
    fclose(fp);

    file->indexer = indexer;
    file->trailer = trailer;
    return file;
}

int store_file_wirte(StoreFile* file, void* data, int32_t size)
{
    int32_t total_size = 0;

    if (!io_check_file_exist(file->path))
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "'%s' is not exist.", file->path);
        CheckParam(false, buf);
        return 0;
    }
    if (file->trailer->type == STORE_FILE_BLOCK)
    {
        int32_t free_size = store_file_get_free_size(file) - sizeof(BlockIndex);
        if (free_size < size)
        {
            return -1;
        }
        total_size = file->trailer->allocated_size;
    }
    else
    {
        total_size = file->trailer->allocated_size + size + sizeof(BlockIndex);
        file->trailer->allocated_size += size + sizeof(BlockIndex);
    }

    int64_t timestamp = timer_get_micro_second();
    Trailer* trailer = file->trailer;
    Indexer* indexer = file->indexer;

    FILE* fp = fopen(file->path, "rb+");

    fp_flush(fp, trailer->end_block_offset, size, data);

    //update chunk index
    BlockIndex* block_index = emalloc(sizeof(BlockIndex));
    //query all this file and get number
    block_index->size = size;
    block_index->offset = trailer->end_block_offset;
    block_index->timestamp = timestamp;
    list_push_back(indexer->indexs, block_index);
    indexer->num++;

    int32_t indexer_size = indexer_get_size(file->indexer);
    int32_t trailer_size = sizeof(Trailer);

    int32_t indexer_start_offset = total_size - trailer_size - indexer_size;
    int32_t used_size = trailer->used_size + size + sizeof(BlockIndex);
    int32_t trailer_offset = total_size - trailer_size;

    //update trailer
    trailer->used_size = used_size;
    trailer->end_block_offset += size;
    trailer->indexer_offset = indexer_start_offset;
    trailer->trailer_offset = trailer_offset;

    //update timestamp
    trailer->start_timestamp = trailer->start_timestamp > timestamp ? timestamp : trailer->start_timestamp;
    trailer->end_timestamp = trailer->end_timestamp < timestamp ? timestamp : trailer->end_timestamp;

    //write to disk
    fseek(fp, trailer->indexer_offset, SEEK_SET);
    indexer_flush(fp, file->indexer);
    fseek(fp, trailer->trailer_offset, SEEK_SET);
    trailer_flush(fp, file->trailer);
    fclose(fp);
    return 1;
}

int store_file_read(StoreFile* file, int64_t start, int64_t end, /*out:Vector<BFile>*/Vector* files)
{
    if (!io_check_file_exist(file->path))
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "'%s' is not exist", file->path);
        CheckParam(false, buf);
        return 0;
    }
    FILE *fp = NULL;
    if ((fp = fopen(file->path, "rb")) == NULL)
    {
        char buf[1024] =
        { 0 };
        sprintf(buf, "Can not open '%s'.", file->path);
        CheckParam(false, buf);
        return 0;
    }
    pthread_rwlock_rdlock(&file->rw_lock);
    BlockIndex* index = NULL;
    iterator_list(file->indexer->indexs)
    {
        index = list_next(file->indexer->indexs);
        if (index->timestamp >= start && index->timestamp <= end)
        {
            BFile* bfile = emalloc(sizeof(BFile) + index->size);
            bfile->size = index->size;
            bfile->timestamp = index->timestamp;
            fp_load(fp, index->offset, index->size, (void**) &bfile->data);
            vector_push_back(files, bfile);
        }
    }
    pthread_rwlock_unlock(&file->rw_lock);
    return 1;
}

const char* store_file_get_file_name(StoreFile* file)
{
    return file->path;
}

const char* store_file_get_namespace(StoreFile* file)
{
    return file->trailer->namespace;
}

StoreFileType store_file_get_type(StoreFile* file)
{
    return file->trailer->type;
}

int32_t store_file_get_status(StoreFile* file)
{
    return file->trailer->status;
}

void store_file_set_status(StoreFile* file, int32_t status)
{
    file->trailer->status = status;
}

int32_t store_file_allocated_size(StoreFile* file)
{
    return file->trailer->allocated_size;
}

int32_t store_file_used_size(StoreFile* file)
{
    return file->trailer->used_size;
}

